//
//  TestRuntimeViewController.m
//  Demos
//
//  Created by iMac on 2018/3/2.
//  Copyright © 2018年 iMac. All rights reserved.
//

#import "TestRuntimeViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "Person.h"
#import "Cat.h"
#import "Person+cg.h"
#import "Cat+extend.h"

@interface TestRuntimeViewController ()

@end

@implementation TestRuntimeViewController

- (void)viewDidLoad {
    self.view.backgroundColor = [UIColor whiteColor];
    [super viewDidLoad];
    [self addDismissButton];
    // Do any additional setup after loading the view.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self test_runtime_15];
}








#pragma mark Runtime




/// 为person类添加一个新的 类方法



/// 为Person 类添加一个新的方法(实例方法) - 在person的分类中实现
- (void)test_runtime_15 {
    Person *p = [[Person alloc] init];
    [p performSelector:@selector(kill) withObject:@"传的值"];
    //    class_addMethod([p class], @selector(newMethod), <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
}
- (void)newMethod {
    NSLog(@"执行 newMethod");
}

/**
 -- 实现在Person的分类文件中
 虽然在 Person的分类中定义了@property的属性height
 但是如果打印Person的成员列表, 依旧没有height的存在, 但是却生成了 getter和setter
 因为类的结构是一个结构体, 不能动态添加成员变量, 但是方法列表可变
 所以可以通过这种方法, 利用getter/setter达到给Person类添加新属性的目的
 */
/// 通过person的分类, 为person天极爱一个属性
- (void)test_runtime_14 {
    Person *p = [[Person alloc] init];
    p.height = 188.f;
    NSLog(@"%f", [p height]);
}
/// 改变Person私有变量
- (void)test_runtime_13 {
    
    Person *per = [[Person alloc] init];
    NSLog(@"%@", per);
    
    unsigned int count = 0;
    Ivar *allList = class_copyIvarList([Person class], &count);
    Ivar ivar = allList[0];
    object_setIvar(per, ivar, @"Mike");
    NSLog(@"改变后的person: %@", per);
}


/// demo 获取 Person 类的所有方法
- (void)test_runtime_12 {
    unsigned int count;
    Method *allMethod = class_copyMethodList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Method md = allMethod[i];
        SEL sel = method_getName(md);
        const char *methodName = sel_getName(sel);
        NSLog(@"methodName : %s", methodName);
    }
}

/// demo 获取 Person 类的所有成员变量
- (void)test_runtime_11 {
    unsigned int count = 0;
    // 获取累的一个包含所有变量的列表, IVar是runtime声明的一个宏, 是实例变量的意思
    Ivar *allVariables = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = allVariables[i];
        const char *variableName = ivar_getName(ivar);
        const char *variableType = ivar_getTypeEncoding(ivar);
        NSLog(@"(name: %s) -- (Type: %s)", variableName, variableType);
    }
}

// ==================   ==================  ==================  ==================  ==================
/// 消息转发
/**
 //  第一步，消息接收者没有找到对应的方法时候，会先调用此方法，可在此方法实现中动态添加新的方法
 //  返回YES表示相应selector的实现已经被找到，或者添加新方法到了类中，否则返回NO
 + (BOOL)resolveInstanceMethod:(SEL)sel {
 return YES;
 }
 
 //  第二步， 如果第一步的返回NO或者直接返回了YES而没有添加方法，该方法被调用
 //  在这个方法中，我们可以指定一个可以返回一个可以响应该方法的对象， 注意如果返回self就会死循环
 - (id)forwardingTargetForSelector:(SEL)aSelector {
 return nil;
 }
 
 
 //  第三步， 如果forwardingTargetForSelector:返回了nil，则该方法会被调用，系统会询问我们要一个合法的『类型编码(Type Encoding)』
 //  若返回 nil，则不会进入下一步，而是无法处理消息
 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
 return [NSMethodSignature signatureWithObjCTypes:"v@:"];
 }
 
 // 当实现了此方法后，-doesNotRecognizeSelector: 将不会被调用
 // 在这里进行消息转发
 - (void)forwardInvocation:(NSInvocation *)anInvocation {
 // 在这里可以改变方法选择器
 [anInvocation setSelector:@selector(unknown)];
 // 改变方法选择器后，需要指定消息的接收者
 [anInvocation invokeWithTarget:self];
 }
 
 - (void)unknown {
 NSLog(@"unkown method.......");
 }
 
 // 如果没有实现消息转发 forwardInvocation  则调用此方法
 - (void)doesNotRecognizeSelector:(SEL)aSelector {
 NSLog(@"unresolved method ：%@", NSStringFromSelector(aSelector));
 }
 */

/// 动态加载方法
/**
 //An Objective-C method is simply a C function that take at least two arguments—self and _cmd.
 void run(id self, SEL _cmd, NSNumber *number){
 NSLog(@"run for %@", number);
 }
 
 //收到run:消息时候，为该类添加一个方法实现
 + (BOOL)resolveInstanceMethod:(SEL)sel{
 if(sel == NSSelectorFromString(@"run:")){
 class_addMethod(self, @selector(run:), run, "v@:@");
 return YES;
 }
 return [super resolveInstanceMethod:sel];
 }
 //另外针对类方法的为 resolveClassMethod
 */

/**
 class_replaceMethod 替换类方法的定义
 method_exchangeImplementations 交换两个方法的实现（具体使用案例如下）
 method_setImplementation 设置一个方法的实现
 注：class_replaceMethod 试图替换一个不存在的方法时候，会调用class_addMethod为该类增加一个新方法
 */
/// 方法交换
- (void)test_runtime_02 {
    Class catClass = objc_getClass("Cat");
    Cat *cat = objc_msgSend(catClass, @selector(alloc));
    cat = objc_msgSend(cat, sel_registerName("init"));
    
    Method eat_m = class_getInstanceMethod(catClass, @selector(eat));
    Method run_m = class_getInstanceMethod(catClass, sel_registerName("run"));
    method_exchangeImplementations(eat_m, run_m);
    objc_msgSend(cat, @selector(run));
    objc_msgSend(cat, @selector(eat));
    
    cat.name = @"cat的name";
    NSLog(@"%@", cat.name);
}
/// 创建对象,调用方法
- (void)test_runtime_01 {
    Class catClass = objc_getClass("Cat");
    Cat *cat = objc_msgSend(catClass, @selector(alloc));
    cat = objc_msgSend(cat, @selector(init));
    // objc_msgSend(cat, @selector(eat));
    objc_msgSend(cat, sel_registerName("eat"));
    
    // 汇总消息传递过程 -- 仔细看了一下, 下边这个方法就是上边方法的综合, 把上边的方法写成了一个
    objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("Cat"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("eat"));
    
}






- (void)addDismissButton {
    UIButton *dismiss = ({
        UIButton *btn = [[UIButton alloc] init];
        [btn setTitle:@"dismiss" forState:UIControlStateNormal];
        [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        CGFloat f = 100;
        btn.frame = CGRectMake(SCREEN_WIDTH - f, SCREEN_HEIGHT - f, f, f);
        [btn setBackgroundColor:[UIColor purpleColor]];
        [btn addTarget:self action:@selector(dismissButtonClick:) forControlEvents:UIControlEventTouchUpInside];
        btn;
    });
    [self.view addSubview:dismiss];
}
- (void)dismissButtonClick:(UIButton *)sender {
    [self dismissViewControllerAnimated:true completion:nil];
}
@end
